Ext.namespace( "Soashable.Lang" );

Soashable.Lang.EN = {
    username: "Username",
    password: "Password",
    forgotPasswordButton: "Forgot Password",
    signupButton: "Sign Up",
    loginButton: "Login",
    
    netSoashable: "Soashable",
    netAim: "AIM", // too long, layout messed up: AOL Instant Messenger
    netMsn: "MSN Messenger",
    netYahoo: "Yahoo!",
    
    signingOnMessage: "Signing on... please wait.",
    
    loginTitle: "Login To Soashable",
    rosterTitle: "Contact List",
    
    registerTitle: "Create a Soashable account!",
    cancelButton: "Cancel",
    registerButton: "Register",
    
    statusAvailable: "Available",
    statusChat: "Feeling Chatty",
    statusAway: "Away",
    statusXA: "Extended Away",
    statusDND: "Do Not Disturb",
    statusInvisible: "Invisible",
    
    settingsTitle: "Settings",
    onlineUsersTitle: "Online Users",
    enterStatusMessage: "Enter your status message...",
    
    addContactButton: "Add",
    removeContactButton: "Remove",
    addGroupButton: "Add Group",
    signoutButton: "Sign Out",
    
    chatTitle: "Instant Messages",
    sendButton: "Send",
    avChatButton: "Start A/V Chat",
    
    addContactTitle: "Add Buddy",
    group: "Group",
    enterUsernameBlank: "Enter full jabber ID...",
    alias: "Alias"
};

/**
 * GUI Manager base for providing a common way for events.
 * 
 * The gui manager will notify the layout  
 */
function DialogManager(soashable) {
    this.soashable = soashable;

    this._LOGIN_DIALOG = "loginDialog";
    this._ROSTER_DIALOG = "rosterDialog";
}

Ext.extend( DialogManager, Ext.util.Observable, {
    getLoginDialog : function() {
        return Ext.WindowMgr.get( this._LOGIN_DIALOG );
    },
    
    createLoginDialog : function() {
        // @todo should be redone to classify scope of gui manager?
        var win = this.getLoginDialog();
    
        if( win != undefined ) {
            throw new Error( "Login Dialog already exists." ); 
        }
        
        return new Soashable.Dialog.LoginDialog({
            id: this._LOGIN_DIALOG, 
            lang: this.soashable.getLang()
        });
    },
    
    getRosterDialog : function() {
        return Ext.WindowMgr.get( this._ROSTER_DIALOG );
    },
    
    createRosterDialog : function(roster) {
        var win = this.getRosterDialog();
    
        if( win != undefined ) {
            throw new Error( "Roster Dialog already exists." ); 
        }
    
        win = new Soashable.Dialog.RosterDialog.Default(this._ROSTER_DIALOG, this, roster, this.soashable.getLang());
        
        var left =  Ext.getBody().getSize().width - win.getSize().width - 10;
        win.setPosition( left );
        
        return win;
    },

    createChatDialog : function(chat) {
        var dlg = this.getChatDialog(chat);
    
        if( dlg != undefined ) {
            throw new Error( "Chat Dialog already exists." ); 
        }

        var id = this.generateChatDialogId(chat);
        dlg = new Soashable.Dialog.ChatDialog(chat, id, this, this.soashable.getLang());

        return dlg;
    },
    
    createAddContactDialog: function(config) {
        var roster = Xmpp4Js.Roster.Roster.getInstanceFor( this.soashable.getConnection() );
        var lang = this.soashable.getLang();
        
        var groups = roster.getGroups();
        var groupNames = [];
        for( var i = 0; i < groups.length; i++ ) {
            var group = groups[i];
            if( !(group instanceof Xmpp4Js.Roster.VirtualRosterGroup) ) {
                groupNames.push( groups[i].name );
            }
        }
        
        // define before the closure for addbuddy is created.
        var addContactDlg = null;
        
        config = Ext.apply( config, {
            title: lang.addContactTitle, 
            lang: lang, 
            groups: groupNames,
            listeners: {
                scope: this,
                addbuddy: function(jid, groupName, alias) {
                    var p = new Xmpp4Js.Packet.RosterPacket();
                    p.addItem( jid, alias, [groupName] );
                    
                    roster.getConnection().send( p, function() {
                        addContactDlg.hide();
                    } );
                }
            }
            //initialUsername: "jerk@somewhere-else.com"
        });
        
        addContactDlg = new Soashable.Dialog.AddBuddyDialog(config);
        
        return addContactDlg;
    },
    
    getChatDialog : function(chat) {
        var id = this.generateChatDialogId(chat);
        
        return Ext.WindowMgr.get( id );
    },
    /**
     * @private
     */
    generateChatDialogId : function(chat) {
        return "chat."+chat.getParticipant().withoutResource().toString();
    },
    getSoashable : function() {
        return this.soashable;
    }
});


function ChatDialogManager() {

}

ChatDialogManager.prototype = {
    openChat : function(toJid) {
    
    },
    
    appendMessage : function(toJid, message) {
    
    },
    
    appendNotice : function(chat) {
        
    }
}
Ext.onReady(function() {
    // for html editor
    Ext.QuickTips.init()
});



function Soashable(lang) {
    this.presenceMode = "auto";
    this.lang = lang;

    this.addEvents({
        auth: true
    });


    this.dialogManager = new DialogManager(this);
    
    
    this._setupStanzaProvider();
    this.con = new Xmpp4Js.Connection({
        transport: {
            clazz: Xmpp4Js.Transport.Script,
            endpoint: "http://*.bosh.soashable.com:7070/http-bind/",
            useKeys: true
        },
        stanzaProvider: this.stanzaProvider,
        listeners: {
            scope : this,
            termerror : this.onTerminate,
            close : this.onClose
        }
    });

    this._setupExtensionProvider(this.con);
    this.initDisco();
    this.initChatManager();
 
    this.con.addPacketListener( this._onPresence.bind(this), new Xmpp4Js.PacketFilter.PacketTypeFilter( Xmpp4Js.Packet.Presence ) );

    this.roster = Xmpp4Js.Roster.Roster.getInstanceFor( this.con );
    
    this.titleManager = new Soashable.TitleManager();
    
    this.taskRunner = new Ext.util.TaskRunner();
    this.titleNotifyTask = new Soashable.TitleNotifyTask( this );
}

Ext.extend( Soashable, Ext.util.Observable, {
    CHAT_OPTIONS : {
        ignoreResource: true,
        ignoreThread: true
    },

    start : function() {
        // setup and show login dialog
        var loginDlg = this.dialogManager.createLoginDialog();
        loginDlg.on({
            scope : this,
            signup : this.onSignupClicked,
            forgotPassword : this.onForgotPassClicked,
            submit : this.onLoginClicked
        
        });
        loginDlg.show();
        

        
        
        // setup and show pissing{me}off dialog
        //var pmoDlg = new Soashable.Dialog.PissingMeOffDialog.Default( function(pmoDlg) { pmoDlg.show() } );
        
        
        // focus the login dialog.
        loginDlg.toFront(); 
    },
    
    register : function() {
        // close the connection if it's already open.'
        if( this.con.isConnected() ) {
            this.con.close();
        }
        
        // show form before the response comes to look fast. odds are it will be
        // back before submission.
        var regDlg = new Soashable.Dialog.RegistrationDialog.Default({
            id: "reg-dlg", 
            con: this.con,
            lang: this.lang,
            listeners: {
                scope: this,
                regsuccess: function(iq) {
                    Ext.MessageBox.show({
                      msg: "You have registered successfully.",
                      buttons: Ext.MessageBox.OK
                    });
                    regDlg.hide();
                },
                regerror: function(iq) {
                    Ext.MessageBox.show({
                        msg: "There was an error registering. TODO more specific information.",
                        icon: Ext.MessageBox.ERROR,
                        buttons: Ext.MessageBox.OK
                    });
                },
                /**
                 * this is triggered by either the doalog closing or a
                 * successful registration.
                 */
                hide: function(){
                    this.con.close();
                }
            }
        });   
        regDlg.show();
        
        this.con.connect( JABBER_DOMAIN );
    },
    
    getConnection : function() {
        return this.con;
    },
    
    getLang: function() {
        return this.lang;
    },
    
    /**
     * Adds necessary packet types to the stanza provider.
     */
    _setupStanzaProvider : function(con) {
        // Stanza provider for soashable client
        //var stanzaProvider = con.getStream().getReader().getStanzaProvider();
        
    this.stanzaProvider = new Xmpp4Js.Packet.StanzaProvider();
    this.stanzaProvider.registerDefaultProviders();
        
        // roster packets
        this.stanzaProvider.register(
            RosterPacketProvider,
            Xmpp4Js.Packet.RosterPacket,
            10
        );
    },
    
    _setupExtensionProvider : function(con) {
        this.extProvider = new Xmpp4Js.Ext.PacketExtensionProvider()
        
        this.extProvider.register( Xmpp4Js.Ext.MessageEvent.XMLNS, Xmpp4Js.Ext.MessageEvent );
        this.extProvider.register( Xmpp4Js.Ext.ChatStates.XMLNS, Xmpp4Js.Ext.ChatStates );
        this.extProvider.register( Soashable.Ext.TokBox.XMLNS, Soashable.Ext.TokBox );
        
        //this.extProvider.register( XHTMLExtension.XMLNS, XHTMLExtension );
    },

    onSignupClicked : function( networkBox ) {
        if( networkBox.getNetwork() == "soashable" ) {
            this.register();
        } else {
            Ext.MessageBox.alert( "Sign Up", "Sign up for "+networkBox.getNetwork() );
        }
    },
    onForgotPassClicked : function( networkBox ) {
        Ext.MessageBox.alert( "Forgot Password", "Forgot password for "+networkBox.getNetwork() );
    },
    onLoginClicked : function( networks ) {
        
        this.con.on("connect", function() { 
            this.onConnectForLogin(networks);  
        }, this, {single: true} );
           
           // set a wait message that will be cleared when the connection closes.
        this.waitMessage = Ext.MessageBox.show({
            closable: false,
            modal: true,
            msg: this.lang.signingOnMessage
        });
        
        this.con.connect( JABBER_DOMAIN );
    },
    
    /**
     * Start a login flow upon connection.
     * @private
     */
    onConnectForLogin: function(networks) {
        
        var username = null, password = null;
        
        for( var i = 0; i < networks.length; i++ ) {
            var network = networks[i];
            if( network.network == "soashable" ) {
                username = network.username;
                password = network.password;
            } else {
                // it's a transport, so login upon auth.
                this.on( "auth", function() {
                  var serviceJid = network.network + "." + this.con.domain /* getDomain() */;

                  this.transportLogin( serviceJid, network.username, network.password );
                }, this, {single:true} );
            }
        }
        
        // start the login flow
        var loginFlow = new Xmpp4Js.Workflow.Login({
            con: this.con,
            listeners: {
                scope: this,
                success: this.onLoginCompleted,
                failure: this.onAuthError
            }
        });
           
        var type = username ? "plaintext" : "anon";
        loginFlow.start( type, username, password );
    },
    
    transportLogin: function( serviceJid, username, password ) {
;;;        console.info( "transportLogin: " + serviceJid + " " + username );

        var regFlow = new Xmpp4Js.Workflow.Registration({
        	con: this.con,
        	toJid: serviceJid /* TODO make getJid() */,
        	listeners: {
        		scope: this,
        		success: function() {
;;;                            console.info( "logged into " + serviceJid + " as " + username );
                        },
        		failure: function() {
                            Ext.MessageBox.alert( "Login Failed", "Failed to log into " + serviceJid + " as " + username );
                            // TODO show reg dialog to retry
                        }
        	}
       	});
       	
       	regFlow.start({
            username: username,
            password: password
        });
        
    },
    
    /**
     * Called when the login has completed successfully
     */
    onLoginCompleted : function() {
        
        this.waitMessage.hide();
        
        this.fireEvent( "auth", this );
        
        this.con.send(this.con.getPacketHelper().createPresence());

        var loginDlg = this.dialogManager.getLoginDialog();
        loginDlg.hide();
        
        var rosterDlg = this.dialogManager.createRosterDialog(this.roster);
        this._setupRosterDialog( rosterDlg );
        rosterDlg.show();
        


    },
    
    onAuthError: function() {
        this.waitMessage.hide();
        
        Ext.MessageBox.alert( "Error Logging In",  "There was an error authenticating." );
        this.con.close();
    },
    
    _setupRosterDialog : function(rosterDlg) {
        rosterDlg.on({
            scope : this,
            itemDblClick : this.onRosterItemDblClick
        });
    },

    onChatStarted : function(chat) {
        var chatDlg = this.dialogManager.createChatDialog( chat );
        
        // show window and "flash" title
        chat.on({
          scope: this,
          messageSent: function(chat, message) {
            this.stopTitleNotify();
          },
          messageReceived: function(chat, message) {
            if( message.hasContent() ) {
                chatDlg.show();
                this.startTitleNotify();
            }
          }
        });

        chatDlg.on({
          scope: this,
          activate: function() {
            // FIXME this is commented out because it stops the initial
            //       message when the window is not focused from doing 
            //       the notification.
            //this.stopTitleNotify();
          },
          deactivate: function() {
            this.stopTitleNotify();
          }
        });

        
        // render the first message but keep it hidden until the first
        // actual message comes in.
        chatDlg.render(Ext.getBody());
    },
    
    onChatMessageReceived : function(chat, messagePacket) {
    },
    
    startTitleNotify: function() {
        this.titleNotifyTask.incrementMessageCount();
        if( !this.titleNotifyTask.isRunning() ) {
          this.taskRunner.start( this.titleNotifyTask );
        }
    },
    
    stopTitleNotify: function() {
        this.taskRunner.stop( this.titleNotifyTask );
        this.titleNotifyTask.reset();
    },
 
    onRosterItemDblClick : function(jid) {
        var chatManager = this.chatManager;

        var chat = null;
        try {
            chat = chatManager.findBestChat( jid, null );
        } catch(e) {
            chat = chatManager.createChat( jid, null );
        }
 
        // chatStarted happens when createChat is called, and the 
        // dialog is created there.
        var chatDlg = this.dialogManager.getChatDialog( chat );

        chatDlg.show();
    },
    
    onTerminate : function( packetNode, title, message ) {
        // HACK write code to properly clean up.
        //document.location.reload();
        alert( "onTerminate: "+message );
    },
    
    onClose : function( con ) {
        // HACK write code to properly clean up.
        //document.location.reload();
        alert( "onClose" );
    },
    
    showConnectionError : function() {
        
        
    },
    /**
     * @private
     */
    initDisco: function() {
        this.disco = ServiceDiscoManager.getInstanceFor( this.con );

        // =====================================================================
        // Say that we support file transfer request
        //
        this.disco.addFeature( "http://jabber.org/protocol/si" );
        this.disco.addFeature( "http://jabber.org/protocol/si/profile/file-transfer" );
        // =====================================================================
    },
    
    /**
     * @private
     */
    initChatManager: function() {

        this.chatManager = Xmpp4Js.Chat.ChatManager.getInstanceFor( this.con );

        this.chatManager.setOptions(this.CHAT_OPTIONS);

        this.chatManager.on({
            scope : this,
            chatStarted : this.onChatStarted,
            messageReceived : this.onChatMessageReceived
        });
    },
    
    _onPresence : function(packet) {
        var jid = packet.getFrom();
        var type = packet.getType();
        var status = packet.getStatus();
        var show = packet.getShow();
        
        var jidObj = new Xmpp4Js.Jid( jid );
        // ignore subscription requests from aim transport.
        if( type == "subscribe" && jidObj.getDomain() == "aim." + this.con.getDomain()  && jidObj.getNode() != "" ) {
            return;
        }
                
        if( this.presenceMode == "auto" ) {
            this._onPresenceAuto( jid, type, status, show );
        } else {
            this._onPresenceManual( jid, type, status, show );
        }
        
    },
    
    _onPresenceManual : function(jid, type, status, show ) {
        // handles presence subscription
        if( type == 'subscribe' ) {
            Ext.MessageBox.show({
                title: "Subscription Request",
                msg: jid + " has requested to receive your presence information. allow?",
                buttons: {
                    yes: "Yes",
                    no: "No",
                    cancel: "Ask Later"
                },
                scope: this,
                fn: function(btn, text){
                    if( btn == "cancel" ) { return; } // do nothing
                    
                    var allow = (btn == "yes");
                    var returnType = allow ? 'subscribed' : 'unsubscribed';
                    
                    //  to, type, show, status, priortiy
                    this.con.send( new Xmpp4Js.Packet.Presence( returnType, jid ) );
                    
                    if( btn == "yes" ) {
                        Ext.MessageBox.confirm( "Return the favor?", 
                            "Do you want to add "+jid+" to your contact list as well?", 
                            function(btn) {
                                if( btn != "yes" ) { return; }
                                this.con.send( new Xmpp4Js.Packet.Presence( "subscribe", jid ) );
                            }, 
                            this 
                        );
                    }
                    
                }
            });
        } else if( type == 'unsubscribe' ) {
            // TODO add 'remove from my list', 're-subscribe' options
            Ext.MessageBox.alert( "Unsubscribed", jid + " has elected to unsubscribe from your presence information." );
            // do we need to send "unsubscribed"?
        } else if( type == 'subscribed' ) {
            Ext.MessageBox.alert( "Subscription Accepted", jid + " has allowed your subscription request." );
        } else if( type == 'unsubscribed' ) {
            // TODO add 'remove from my list', 're-subscribe' options
            Ext.MessageBox.alert( "Subscription Denied", jid + " has denied your subscription request." );
        }
    },
    
    /**
     * Accepts and subscribes to any requests. Only alerts upon unsubscription denied.
     */
    _onPresenceAuto : function(jid, type, status, show ) {
        // handles presence subscription
        if( type == 'subscribe' ) {
;;;            console.info( "Accepting and returning subscription for " + jid );
            
            this.con.send( new Xmpp4Js.Packet.Presence( "subscribed", jid ) );
            this.con.send( new Xmpp4Js.Packet.Presence( "subscribe", jid ) );
        } else if( type == 'unsubscribed' ) {
            // TODO add 'remove from my list', 're-subscribe' options
            Ext.MessageBox.alert( "Subscription Denied", jid + " has denied your subscription request." );
        }
    },
    
    getExtensionProvider : function() {
        return this.extProvider;
    }
});


/**
 * @class "flashes" the title bar with new messages. To be used with Ext's TaskRunner.
 */
Soashable.TitleNotifyTask = function(soashable) {
    this.scope = this;
    this.soashable = soashable;
}

Soashable.TitleNotifyTask.prototype = {
        isDefault : true,
        messageCount : 0,
        _isRunning : false,
        interval: 1200,
        
        run: function() {
            this._isRunning = true;

            if( this.isDefault ) {
              this.soashable.titleManager.set( this.messageCount+" New Message(s)" );
            } else {
              this.soashable.titleManager.setDefault();
            }

            // toggle
            this.isDefault = !this.isDefault;
        },

        reset: function() {
            this.soashable.titleManager.setDefault();

            this.isDefault = true;
            this._isRunning = false;
            this.messageCount = 0;
        },
        isRunning: function() {
            return this._isRunning;
        },
        incrementMessageCount: function() {
            this.messageCount++;
        }

  }

/**
 * @class Manages the title and soon the FavIco of the window. Might be
 *        renamed to WindowUtil or similar. Default title is derived from the
 *        textContent of the title element.
 */
Soashable.TitleManager = function() {
    if( Ext.isIE ) {
      this.baseTitle = document.getElementsByTagName("title")[0].innerText;
    } else {
      this.baseTitle = document.getElementsByTagName("title")[0].textContent;
    }
};

Soashable.TitleManager.prototype = {
  setDefault: function() {
      document.title = this.baseTitle;
  },
  set: function(title) {
      document.title = title + " - " + this.baseTitle;
  },
  get: function() {
      return document.title;
  }
}
Ext.namespace( "Soashable.Widget" );

/**
 * @class A simple status box that has a 'statuschange' event that can be reacted to.
 */
Soashable.Widget.StatusBox = function(config) {
    /** @private */
    this.showComboId = Ext.id();
    /** @private */
    this.statusTextId = Ext.id();
    
    this.lang = config.lang;

    config = Ext.apply( config, {
        height: 50,
        layout: 'table',
        layoutConfig: {
            columns: 1
        },
        items: [{
            xtype: 'combo',
            id: this.showComboId,
            mode: 'local',
            store: this.getShowStore(),
            tpl: Soashable.Widget.StatusBox.showTemplate,
            displayField: 'text',
            valueField: 'show',
            value: 'available',
            editable: false,
            forceSelection: true,
            triggerAction: 'all',
            width: 250,
            listeners: {
                scope: this,
                select: this.onChange
            }
        },
        {
            xtype: 'textfield',
            id: this.statusTextId,
            width: 250,
            emptyText: this.lang.enterStatusMessage,
            listeners: {
                scope: this,
                blur: this.onChange
            }
        }]
    });

    Soashable.Widget.StatusBox.superclass.constructor.call( this, config );
    
    this.addEvents({
        /**
         * @event statuschange
         * @param {String} show
         * @param {String} status
         */
        'statuschange' : true
    });

}


/**
 * The template to be shown in the status dropdown. Has 'text', 'show', 'icon' and 'color tokens.
 */
Soashable.Widget.StatusBox.showTemplate = '<tpl for="."><div style="color: {color};" class="x-combo-list-item"><img src="{icon}" alt="{text}"/> {text}</div></tpl>';


Soashable.Widget.StatusBox.prototype = {
    /**
     * Fired when the combobox value changes or the text value is blurred.
     * @private
     */
    onChange : function() {
        var showField = Ext.ComponentMgr.get(this.showComboId);
        var statusField = Ext.ComponentMgr.get(this.statusTextId);

        var status = statusField.getValue();
        var show = showField.getValue();

        this.fireEvent( "statuschange", show, status );
    },
    
    /**
     * Creaets and returns a store with all status states, merged
     * with the lang object:
     * row data: 'text', 'show', 'icon', and 'color' fields.
     * @private
     */
    getShowStore: function(config) {
        
        var config = {
            fields: ['text', 'show', 'icon', 'color'],
            data : [
                ['statusChat',  'chat',         'images/status/chat.png',     'green'],
                ['statusAvailable',       'available',    'images/status/available.png',     'black'], 
                ['statusAway',            'away',         'images/status/away.png',     'gray'], 
                ['statusXA',   'xa',           'images/status/extended-away.png',     'gray'], 
                ['statusDND',  'dnd',          'images/status/busy.png',     'red'],
                ['statusInvisible',       'invisible',    'images/status/invisible.png',     'silver']
            ]
        }
        
        for( var i = 0; i < config.data.length; i++) {
            var row = config.data[i];
            
            // the title row will be the name of a string,
            // replace it with the actual string.
            row[0] = this.lang[ row[0] ];
        }
        return new Ext.data.SimpleStore(config);
    }
}

Ext.extend( Soashable.Widget.StatusBox, Ext.Panel, Soashable.Widget.StatusBox.prototype);

Ext.ComponentMgr.registerType( "soashable.statusbox", Soashable.Widget.StatusBox );
Ext.namespace( "Soashable.Widget" );

/**
 * @class Renders a flash player with TokBox, using the user's widget and user info.
 */
 
Soashable.Widget.TokBox = function(config) {
    /**
     * @private
     * @type {Ext.Element}
     */
    this.el = null;
    
    var superConfig = Ext.apply( config, {
        width: 540,
        height: 260
    });
    
    Soashable.Widget.TokBox.superclass.constructor.call( this, superConfig );
}

/**
* Used as the main box (i.e. that everything is rendered into)
* @private
*/
Soashable.Widget.TokBox.boxTemplate = new Ext.Template( '<div class="tokbox" id="{id}" style="width: {width}px; height: {height}px;"><object type="application/x-shockwave-flash" data="http://www.tokbox.com/f/{tokboxWidget}" width="{width}" height="{height}"><param name="movie" value="http://www.tokbox.com/f/{tokboxWidget}"></param><param name=FlashVars value="user={tokboxUserId}&pass={tokboxPasswordMd5}"></param></object></div>' );

Soashable.Widget.TokBox.prototype = {    
    /**
    * Handle the actual rendering of the component.
    * @private
    */
    onRender : function(ct, position) {
        var tplVars = {
            id: this.getId(),
            tokboxWidget: this.initialConfig.widget,
            tokboxUserId: this.initialConfig.userId,
            tokboxPasswordMd5: hex_md5(this.initialConfig.password),
            
            height: this.initialConfig.height,
            width: this.initialConfig.width
        };
        
        var el = Soashable.Widget.TokBox.boxTemplate.append( ct, tplVars );
        el = Ext.get( el )
        this.el = el;
        
        this.setSize(this.initialConfig.width, this.initialConfig.height);
    }
}

Ext.extend( Soashable.Widget.TokBox, Ext.BoxComponent, Soashable.Widget.TokBox.prototype );

Ext.ComponentMgr.registerType( "soashable.tokbox", Soashable.Widget.TokBox );
function PopoutWindow(config) {
    var config = Ext.apply( config, {
    	
    	listeners: {
    		scope: this,
    		titlechange: this.onTitleChange
    	}
    });
    
    // do not popin on close by default
    this.popinOnClose = config.popinOnClose != undefined ? config.popinOnClose : false;
    
    this.isPopout = false;
	
    PopoutWindow.superclass.constructor.call( this, config );
}

PopoutWindow.template = '<html><head><script type="text/javascript">window.onload = onPopoutOpened; window.onunload = onPopoutClosed;</script><!-- TODO load this dynamically --><link rel="stylesheet" type="text/css" href="ext-2.0.1/resources/css/ext-all.css"/></head><body></body></html>';

PopoutWindow.prototype = {
    popout: function() {
	var id = "soashable";
	
	var box = this.getBox();
	
	var x = window.screenX + box.x;
	// this is a rough approximation of the position. need to experiment more for exact.
	var y = window.screenY + box.y + ((window.outerHeight - window.innerHeight) / 2);
	var h = box.height;
	var w = box.width;
	
	var props = "toolbar=no,status=no,location=no,menubar=no,resizable=yes,scrollbars=no,height="+h+",width="+w+",screenX="+x+",screenY="+y;

	// open the popout window 
	this.popoutWin = window.open( "popout.html", id, props );
	
	
	// the popout calls onPopoutOpened in onload
	var self = this;
	this.popoutWin.onPopoutOpened = function() {
		self.onPopoutOpened();
	}
	// this is called by onunload
	this.popoutWin.onPopoutClosed = function() {
		self.onPopoutClosed();	
	}
/*
	// load the document. close triggers onload to be called.
	this.popoutWin.document.open();
	this.popoutWin.document.write( PopoutWindow.template );
	this.popoutWin.document.close();
*/	
	// and finally, mark us as being a popout for events, etc
	this.isPopout = true;
    },
    
    popin: function() {
	var popoutDoc = this.popoutWin.document;
         
        var mainComponent = this;

	// create a new, empty body node in the place of the old. remove the old.
	// TODO investigate if we even need to remove the old one, or if applyToMarkup
	//      finds any differences.
	var newNode = this.oldBody.dom.cloneNode( false );
	// FIXME IE(7) has trouble with 'No such interface supported'
	newNode = this.oldBody.dom.parentNode.insertBefore( newNode, this.oldBody.dom );
	this.oldBody.dom.parentNode.removeChild( this.oldBody.dom );

	// set the body tothe new location and render
        mainComponent.body = Ext.get(newNode);
        mainComponent.applyToMarkup( mainComponent.getEl() );
        
        // TODO set position/size (within bounds of document)
        //      could actually be the job of listeners.
        
        // show the window again
        this.show();
        
        // we're no longer intersted in acting like a popout.
        this.isPopout = false;
        
	// close the external popup 
	// note: onPopoutClosed doesn't affect anything, since it ignores us while not a popout.
        this.popoutWin.close();
        this.popoutWin = null;
    },
    
    onPopoutOpened: function() {
	var popoutDoc = this.popoutWin.document;
	
	this.popoutWin.document.title = this.title;
	
	// TODO copy css
         
        var mainComponent = this;
   
   	// keep a reference to the old body for if/when we popin
        this.oldBody = mainComponent.body;
        
        // set the body to the document body of the new window and 
        // render it.
        // TODO make space for toolbars/buttons/etc on the document, and set BG color.
        mainComponent.body = Ext.get(popoutDoc.body);
        mainComponent.applyToMarkup( mainComponent.getEl() );
        
        // hide the embedded window.
        this.hide();
    },
    
    onPopoutClosed: function() {
    	if( !this.isPopout ) { return; }
    	
    	if( this.popinOnClose ) {
    	    this.popin();
        } else {
        	// TODO look into closeAction for hide/destroy stuff.
        	this.close();
        }
    },
    
    onTitleChange: function( panel, newTitle ) {
    	if( !this.isPopout ) { return; }
    	
    	this.popoutWin.document.title = newTitle;	
    }
}

Ext.extend( PopoutWindow, Ext.Window, PopoutWindow.prototype );


Ext.namespace("Soashable.Ext");

/**
 * @constructor
 * @extends Xmpp4Js.Ext.PacketExtension
 */
Soashable.Ext.TokBox = function(stanza, widget) {
    Soashable.Ext.TokBox.superclass.constructor.call( this, stanza );

    if( widget ) {
        this.setWidget( widget );
    }
}

Soashable.Ext.TokBox.XMLNS = "soashable:app:tokbox";

Soashable.Ext.TokBox.prototype = {
    
    getElementNS : function() {
        return Soashable.Ext.TokBox.XMLNS;
    },
    setWidget: function(widget) {
        
        // add the new state
        this.getNode().setAttribute( "widget", widget);
    },
    
    getWidget: function() {
        return this.widget;
    },
    
    readNode : function() {
        Soashable.Ext.TokBox.superclass.readNode.call( this );
        
        // FIXME this is potentially flaky... if there are text nodes, etc.
        this.widget = this.getNode().getAttribute("widget");
    },
    createNode : function(widget) {
        Soashable.Ext.TokBox.superclass.createNode.call(this);
        
        if( widget ) {
            this.setWidget( widget );
        }
    }
};

Ext.extend( Soashable.Ext.TokBox, Xmpp4Js.Ext.PacketExtension, Soashable.Ext.TokBox.prototype );
Ext.namespace( "Soashable.Dialog" );


/**
 * @class
 */
Soashable.Dialog.LoginDialog = function(config) {
    this.lang = config.lang;
    
    var superConfig = Ext.apply( config, {
        title: "Login To Soashable",
        width: 545,
        bodyStyle: 'padding: 7px',

        resizable: false,
        closable: false,

        layout: 'table',
        layoutConfig: {
            columns: 2
        },
        defaults: {
            bodyStyle:'margin:3px; padding: 7px'
        },
        items: [
            {
                xtype: "soashable.loginbox.network",
                network: "soashable",
                title: this.lang.netSoashable,
                lang: this.lang,
                listeners: {
                    scope: this,
                    signup: this.onSignup,
                    forgotPassword: this.onForgotPassword
                }
            },
            {
                xtype: "soashable.loginbox.network",
                network: "aim",
                title: this.lang.netAim,
                lang: this.lang,
                listeners: {
                    scope: this,
                    signup: this.onSignup,
                    forgotPassword: this.onForgotPassword
                }
            },
            {
                xtype: "soashable.loginbox.network",
                network: "msn",
                title: this.lang.netMsn,
                lang: this.lang,
                listeners: {
                    scope: this,
                    signup: this.onSignup,
                    forgotPassword: this.onForgotPassword
                }
            },
            {
                xtype: "soashable.loginbox.network",
                network: "yahoo",
                title: this.lang.netYahoo,
                lang: this.lang,
                listeners: {
                    scope: this,
                    signup: this.onSignup,
                    forgotPassword: this.onForgotPassword
                }
            }/*,
            {
                xtype: "soashable.loginbox.network",
                network: "jabber",
                lang: this.lang,
                listeners: {
                    scope: this,
                    signup: this.onSignup,
                    forgotPassword: this.onForgotPassword
                }
            }*/
        ],
        buttons: [{
            id: "login-button",
            text: this.lang.loginButton,
            icon: "images/status/log-in.png",
            cls: "x-btn-text-icon",
            scope: this,
            handler: this.onLogin
        }]
    });

    Soashable.Dialog.LoginDialog.superclass.constructor.call( this, superConfig );

    this.addEvents({
        /**
         * @event submit
         * @param {Array} networks - an array of objects with network, username and password properites. A network will only appear if username is set.
         */
        submit : true,
        /**
         * @event signup
         * @param {Soashable.Dialog.LoginDialog.Network} networkBox
         */
        signup : true,
        /**
         * @event forgotPassword
         * @param {Soashable.Dialog.LoginDialog.Network} networkBox
         */
        forgotPassword : true
    });
}

Soashable.Dialog.LoginDialog.prototype = {
    onSignup: function(networkBox) {
        this.fireEvent( "signup", networkBox );
    },
    onForgotPassword: function(networkBox) {
        this.fireEvent( "forgotPassword", networkBox );
    },
    onLogin: function() {
        var networks = [];

        this.items.each( function(item) {
            if( item instanceof Soashable.Dialog.LoginDialog.Network && item.getUsername() ) {
                networks.push({
                    network: item.getNetwork(),
                    username: item.getUsername(),
                    password: item.getPassword()
                });
            }
        }, this );

        this.fireEvent( "submit", networks );
    }
}

Ext.extend( Soashable.Dialog.LoginDialog, Ext.Window, Soashable.Dialog.LoginDialog.prototype );


Soashable.Dialog.LoginDialog.Network = function(config) {

    this.title = config.title;
    this.lang = config.lang;
    this.network = config.network;
    // TODO these should be unique per dialog
    this.usernameId = this.network+"-username";
    this.passwordId = this.network+"-password";

    var superConfig = Ext.apply( config, {
        layout: 'form',
        title: null, // cancels out the title we passed in, because that makes it ugly using ext panel title.
        defaultType: 'textfield',
        defaults: {
            // applied to each contained item
            width: 130,
            msgTarget: 'side'
        },

        items: [
        {
            xtype: 'panel',
            fieldLabel: false,
            border: false,
            html: '<h1><img src="images/protocols/'+this.network+'.png" alt="'+this.title+' Logo"/> ' + this.title + '</h1>'
        },
        {
            fieldLabel: this.lang.username,
            name: 'username',
            allowBlank: true,

            id: this.usernameId
        },{
            fieldLabel: this.lang.password,
            name: 'password',
            allowBlank: true,
                        inputType: "password",

            id: this.passwordId
        }
        
        ],

        buttons: [{
            id: this.network+"-forgotpass-button",
            text: this.lang.forgotPasswordButton,
            scope: this,
            handler: this.onForgotPassword
        },
        {
            id: this.network+"-signup-button",
            text: this.lang.signupButton,
            scope: this,
            handler: this.onSignUp
        }]
    });

    Soashable.Dialog.LoginDialog.Network.superclass.constructor.call( this, superConfig );

    this.addEvents({
        /**
         * @event signup
         * @param {Soashable.Dialog.LoginDialog.Network} networkBox
         */
        signup : true,
        /**
         * @event forgotPassword
         * @param {Soashable.Dialog.LoginDialog.Network} networkBox
         */
        forgotPassword : true
    });
}

Soashable.Dialog.LoginDialog.Network.prototype = {
    getUsername: function() {
        var c = Ext.ComponentMgr.get( this.usernameId );
        return c.getValue();
    },

    getPassword: function() {
        var c = Ext.ComponentMgr.get( this.passwordId );
        return c.getValue();
    },

    getNetwork: function() {
        return this.network
    },

    onForgotPassword: function() {
        this.fireEvent( "forgotPassword", this );
    },

    onSignUp: function() {
        this.fireEvent( "signup", this );
    }

}

Ext.extend( Soashable.Dialog.LoginDialog.Network, Ext.Panel, Soashable.Dialog.LoginDialog.Network.prototype );

Ext.ComponentMgr.registerType( "soashable.loginbox.network", Soashable.Dialog.LoginDialog.Network );

// Copyright (C) 2007  Harlan Iverson <h.iverson at gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


Ext.namespace( "Soashable.Dialog.RosterDialog" );

/**
* Initialize RosterWindow and add onRosterUpdate as a listener to roster, and
* reload roster to get all updates.
*
* TODO use a config object
*
* @param roster Roster the roster to bind to
*/
Soashable.Dialog.RosterDialog.Default = function( winId, dialogManager, roster, lang) {
    this.dialogManager = dialogManager;
    this.roster = roster;
    this.lang = lang;
    
    var config = {
        id: winId,
        height : 400,
        width : 300,
        title : this.lang.rosterTitle,
        renderTo: document.body,
        closable: false,
        tbar: [{ 
            text: this.lang.addContactButton, 
            icon: "images/roster/plus.gif",
            cls: 'x-btn-text-icon add-contact',
            handler: this._onAddContact,
            scope: this
        },
        { 
            text: this.lang.removeContactButton, 
            icon: "images/roster/minus.gif",
            cls: 'x-btn-text-icon remove-contact',
            handler: this.onDeleteContact,
            scope: this
        },
        { 
            text: this.lang.addGroupButton, 
            icon: "images/roster/plus.gif",
            cls: 'x-btn-text-icon add-group',
            handler: this.onAddGroup,
            scope: this
        },
        { 
            text: this.lang.signoutButton, 
            cls: 'x-btn-text-icon signout',
            handler: this.onSignout,
            scope: this
        }],
        layout: 'border',
        items: [{
                region: 'center',
                layout: 'accordion',
                border: false,
                layoutConfig: {
                    animate: false
                },
                items: [{
                    title: this.lang.onlineUsersTitle,
                    id: "roster-tree",
                    xtype: "xmpp4js.rostertree",
                    lang: this.lang,
                    rim: roster.getRosterItemManager(), 
                    pm: roster.getPresenceManager(), 
                    con: roster.getConnection(),
                    lines: false,
                    listeners: {
                        scope: this,
                        "itemcreated": this._onEntryNodeCreated,
                        "groupcreated": this._onGroupNodeCreated,
                        "itemmoved": this.onItemMoved,
                        "render": function() {
                            roster.reload();
                        }
                    },
                    tools:[{
                        id:'refresh',
                        on:{
                            click: function(){
                                roster.reload();
                            },
                            scope: this
                        }
                    }]
                },
                {
                    title: this.lang.settingsTitle,
                    html: "I love settings."
                }]
        },
        {
            region: 'south',
            xtype: "soashable.statusbox",
            lang: this.lang,
            listeners: {
                scope: this,
                statuschange: this.onStatusChange
            }

        }]
    };
    
    Soashable.Dialog.RosterDialog.Default.superclass.constructor.call( this, config );
    
    this.addEvents({
        "itemDblClick" : true,
        "groupDblClick" : true
    });
    
    this.createGroupContextMenu();
}


Ext.extend(Soashable.Dialog.RosterDialog.Default, Ext.Window, {
    _onAddContact: function(o, e) {
        var selectedGroup = undefined;
        
        var tree = Ext.getCmp( "roster-tree" );
        var selNode = tree.getSelectionModel().getSelectedNode();
        if( selNode != null && selNode.attributes.type == "group" ) {
            selectedGroup = selNode.attributes.groupName
        }
        
        var addContactDlg = this.dialogManager.createAddContactDialog({
            initialGroup: selectedGroup
        });
        addContactDlg.show();
    },
    
    onDeleteContact: function() {
        var selectedJid = undefined;
        
        var tree = Ext.getCmp( "roster-tree" );
        var selNode = tree.getSelectionModel().getSelectedNode();
        
        // HACK getSelection doesn't seem to recognize node that was context-clicked on.
        var selNode = this.selectedItem || selNode;

        if( selNode != null && selNode.attributes.type == "item" ) {
            selectedJid = selNode.attributes.jid;

            Ext.MessageBox.confirm( "Delete Contact", "Are you sure you want to completely remove "+selectedJid+" from your contact list?", function(btn) {
                if( btn == "yes" ) {
                    var p = new Xmpp4Js.Packet.RosterPacket();
                    p.addItem( selectedJid, null, null, "remove" );

                    this.roster.getConnection().send( p );
                }
            }, this);
        }
    },
    
    onAddGroup: function() {
        var tree = Ext.getCmp( "roster-tree" );

        var prompt = Ext.MessageBox.prompt( "Create Group", "Enter the name of the group", function(btn, text) {
            if( btn == "ok" ) {
                tree.createGroup( text );
            }
        } );
    },
    
    onSignout: function() {
        this.roster.getConnection().close();
    },

    onItemMoved: function(node, oldParent, newParent) {
        var jid = node.attributes.jid;
        
        var rim = this.roster.getRosterItemManager();
        
        var entry = rim.get( jid );
        // create a list of names starting with the new group
        var groupNames = [ newParent.attributes.groupName ];
        
        // get all groups except the old group
        var groups = entry.getGroups();
        for( var i = 0; i < groups.length; i++ ) {
            var groupName = groups[i].name;
            if( groupName != oldParent.attributes.groupName ) {
                groupNames.push( groupName );
            }
        }
        
        var packet = new Xmpp4Js.Packet.RosterPacket();
        packet.addItem( jid, entry.alias, groupNames );

        this.roster.getConnection().send( packet ); 
    },

    _onRosterTreeItemDblClick: function(node) {
        var jid = node.attributes.jid;
        this.fireEvent( "itemDblClick", jid );
    },
    
    onStatusChange: function(show, status) {
        var con = this.roster.getConnection();
        
        if( show == "invisible" ) {
            Ext.Msg.alert( "Not Implemented", "Invisibility is not implemented yet." );
        } else {
            var pres = new Xmpp4Js.Packet.Presence( "available", null, status, show );
            con.send( pres );
        }
    },

    /**
     * Creates the (right click) context menu that is associated with group entries.
     */
    createGroupContextMenu: function() {

        var clickHandler = function() {
            Ext.Msg.alert( "Not Implemented", "Not implemented" );
            Ext.menu.MenuMgr.get("groupContextMenu").hide();
        }

        var groupContextMenu = new Ext.menu.Menu({
            id: 'groupContextMenu',
            items: [
                {
                    text: 'Add Contact',
                    scope: this,
                    handler: this._onAddContact
                },
                {
                    text: 'Delete Group',
                    scope: this,
                    handler: clickHandler
                }
            ]
        });
        
        var itemContextMenu = new Ext.menu.Menu({
            id: 'itemContextMenu',
            items: [
                {
                    text: 'Delete',
                    scope: this,
                    handler: this.onDeleteContact
                }
            ]
        });

    },

    _onEntryNodeCreated: function( entryNode, groupNode ) {
        entryNode.on( {
            dblclick: this._onRosterTreeItemDblClick,
            contextmenu: function(node, e) {
                
                // HACK getSelection doesn't seem to recognize node that was context-clicked on.
                this.selectedItem = node;
                
                Ext.menu.MenuMgr.get("itemContextMenu").show( node.ui.getAnchor() );
            },
            scope: this
        } );
/*
        var jid = new Xmpp4Js.Jid( entryNode.attributes.jid );

        if( jid.getDomain() == "aim.soashable.com" && jid.getNode() == "" ) {
            entryNode.getUI().addClass( "x-transport" );
        } else if( jid.getDomain() == "aim.soashable.com" ) {
        entryNode.getUI().addClass( "x-network-aim" );
    }
    */
    },

    _onGroupNodeCreated: function( groupNode ) {
        groupNode.on( {
            //dblclick: this._onRosterTreeItemDblClick,
            contextmenu: function(node, e) {
                // HACK getSelection doesn't seem to recognize node that was context-clicked on.
                this.selectedItem = node;
                
                Ext.menu.MenuMgr.get("groupContextMenu").show( node.ui.getAnchor() );
            },
            scope: this
        } );
    },

    _onUpdateGroupsEntryNode: function( entry, groups ) {
        var packet = new Xmpp4Js.Packet.RosterPacket();
        packet.addItem( entry.jid, entry.alias, groups );

        this.roster.getConnection().send( packet );
    }


});


// Copyright (C) 2007  Harlan Iverson <h.iverson at gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Ext.namespace( "Soashable.Dialog" );

/**
 * Create a Chat dialog that is tabbed. 
 *
 * @param chat {Xmpp4Js.Chat.Chat} Optional. The initial chat object to bind with.
 * @param id {String} The ID of the chat window.
 * @param dialogManager {DialogManager} blah.
 * @constructor
 */
Soashable.Dialog.ChatDialog = function(chat, id, dialogManager, lang) {
    this.dialogManager = dialogManager;
    this.lang = lang;
    
    this.tabPanelId = Ext.id();
    
    var config = {
        closeAction: 'hide', 
        title: this.lang.chatTitle,
        id : id,
        width: 400,
        height: 425,
        layout: 'fit',
        items: {
            id: this.tabPanelId,
            xtype: 'tabpanel',
            activeTab:0,
            border:false,
            defaults:{closable: true},
            enableTabScroll: true,
            resizeTabs: true,
            layoutOnTabChange:true
        },
        buttons: [{
            text: "Pop out",
            handler: function() {
                this.popout();
            },
            scope: this
        },{
            text: "Pop in",
            handler: function() {
                this.popin();
            },
            scope: this
        }]
    };

    Soashable.Dialog.ChatDialog.superclass.constructor.call( this, config );
    
    if( chat ) {
        this.addTab( chat );
    }
    
    var tabPanel = Ext.ComponentMgr.get( this.tabPanelId );
    tabPanel.on({
        scope: this,
        remove: this.onTabRemoved
    });
}

Soashable.Dialog.ChatDialog.prototype = {
    /**
     * Add a conversation tab to the window.
     *
     * @param chat {Xmpp4Js.Chat.Chat} The chat object to bind with.
     */
    addTab : function(chat) {
        var tabPanel = Ext.ComponentMgr.get( this.tabPanelId );
        var added = tabPanel.add(new Soashable.Dialog.ChatDialog.Tab(chat, this.dialogManager, this, this.lang));
            
            //this.recv = added.items[0];
            //this.send = added.items[1];
            //this.sendButton = added.items[2].buttons[1];
    },
    
    onTabRemoved : function( tabPanel, tab ) {
        if( tabPanel.items.getCount() == 0 ) {
            this.close();
        }
    }
};
    
Ext.extend( Soashable.Dialog.ChatDialog, PopoutWindow, Soashable.Dialog.ChatDialog.prototype);


/**
 * @constructor
 */
Soashable.Dialog.ChatDialog.Tab = function(chat, dialogManager, parentWin, lang) {
    this.lang = lang;

    this.chatRendererId = Ext.id();
    this.editorId = Ext.id();
    this.avContainerId = Ext.id();
    
    this.chat = chat;
    this.dialogManager = dialogManager;
    this.parentWin = parentWin;

    var to = chat.getParticipant();

    chat.on({
        scope: this,
        "messageReceived": this.onMessageReceived,
        "messageSent": this.onMessageSent,
        "close" : this.onChatClosed
    });

    var superConfig = {
        title: to, 
        closable: true,
        layout: 'border',
        tbar: [{ 
            text: this.lang.addContactButton, 
            cls: 'x-btn-text-icon add-contact',
            handler: this.onAddContact,
            scope: this
        }],
        items: [{
            region: 'center',
            layout: 'border',
            
            items: [{
                region: 'north',
                collapsible: false,
                split: true,

                height: 200,
                autoScroll: true,

                items: [{
                    xtype: "xmpp4js.chatrenderer",
                    id: this.chatRendererId
                }]
            },
            {
                region: 'center',
                collapsible: false,
                id: this.editorId,
                xtype: 'textarea',
                listeners: {
                    scope: this,
                    render: function(comp, e) {
                        var nav = new Ext.KeyNav(this.editorId, {
                            scope: this,
                            "enter": this.onSendClicked
                        });

                    }
                }

                /*
                xtype:'htmleditor',

                hideLabel: true,
                stateEvents: {
                    keyup: function() { alert("omfg"); }
                },
                enableSourceEdit: false,
                enableLists: false,
                enableAlignments: false
                */



            },
            {
                region: 'south',

                buttons: [
                /*{
                    scope: this,
                    handler: function() {
                        var cr = Ext.ComponentMgr.get( this.chatRendererId );
                        for(var i = 0; i < 10; i++ ) {
                            cr.appendMessage( "somebody@somewhere.com", "Lorem ipsum.", "red" );
                        }
                        cr.scrollToBottom();
                    },
                    text: "Send Fake"
                },*/
                {
                    scope: this,
                    handler: this.onAVChatClicked,
                    text: this.lang.avChatButton
                },
                {
                    scope: this,
                    handler: this.onSendClicked,
                    text: this.lang.sendButton
                }]
            }]
        },
        {
            region: 'east',
            
            id: this.avContainerId
        }]

    };
    
    Soashable.Dialog.ChatDialog.Tab.superclass.constructor.call( this, superConfig );
}

Soashable.Dialog.ChatDialog.Tab.prototype = {
    
        onSendClicked : function() {
            var editor = Ext.ComponentMgr.get( this.editorId );
            
            // TODO xhtml extension
            var msg = this.chat.createMessage( editor.getValue() );
            this.chat.sendMessage( msg );

            editor.setValue("");
        },
        
        onAVChatClicked: function() {
            Ext.Ajax.request({
                url: "/app/tokbox", 
                method: "GET",
                params:{
                    op: "getInfo", 
                    email: "testing1@soashable.com"
                },
                scope: this,
                callback: this.onTokBoxServletResponse_Host
            });
        },
        
        onTokBoxServletResponse_Host: function(options, success, response) {
            if( success ) {
                var retObj = Ext.util.JSON.decode(response.responseText);

                var avContainer = Ext.ComponentMgr.get( this.avContainerId );

                var comp = avContainer.add({                            
                    xtype: "soashable.tokbox",
                    widget: retObj.widget_id, // use our own widget ID
                    userId: retObj.user_id,
                    password: retObj.password
                });
                
                var msg = this.chat.createMessage("Join me in this audio/video chat.");
                
                var extProvider = this.dialogManager.getSoashable().getExtensionProvider();
                extProvider.create( Soashable.Ext.TokBox.XMLNS, msg, retObj.widget_id );
                
                this.chat.sendMessage( msg );
                
                // HACK this is freaking stupid, but I'm tired of spending
                //      time dealing with it right now
                avContainer.setSize( 540, 260 );
                
                this.doLayout();
                var parentSize = this.parentWin.getSize();
                this.parentWin.setWidth( parentSize.width + 540);
            } else {
                alert( "wtf? failed!" );
;;;                console.dir( response );
            }
        },
        
        onTokBoxServletResponse_Join: function(options, success, response) {
            if( success ) {
                var retObj = Ext.util.JSON.decode(response.responseText);

                var avContainer = Ext.ComponentMgr.get( this.avContainerId );

                var comp = avContainer.add({                            
                    xtype: "soashable.tokbox",
                    widget: options.options.remoteWidget, // use the widget ID that was setn
                    userId: retObj.user_id,
                    password: retObj.password
                });
                
                // HACK this is freaking stupid, but I'm tired of spending
                //      time dealing with it right now
                avContainer.setSize( 540, 260 );
                
                this.doLayout();
                var parentSize = this.parentWin.getSize();
                this.parentWin.setWidth( parentSize.width + 540);
            } else {
                alert( "wtf? failed!" );
;;;                console.dir( response );
            }
        },
        
        /**
         * Fires when a chat message was received (including first).
         * It is safe to assume that chatStarted will be invoked before this.
         * 
         * @param chat {Xmpp4Js.Chat.Chat} The chat the message is related to
         * @param message {Xmpp4Js.Packet.Message} The incoming message
         * @private
         */
        onMessageReceived : function( chat, message ) {
            var cr = Ext.ComponentMgr.get( this.chatRendererId );
            var editor = Ext.ComponentMgr.get( this.editorId );
            
            this.handleExtensions( message );

            if( message.hasContent() ) {
                cr.appendMessage( message.getFrom(), message.getBody(), "red" );
                cr.scrollToBottom();
            }
        },
        
        handleExtensions : function( message ) {
            var cr = Ext.ComponentMgr.get( this.chatRendererId );
            
            var extProvider = this.dialogManager.getSoashable().getExtensionProvider();
            message.loadExtensions( extProvider );
            
            var msgEvent = message.getExtension( Xmpp4Js.Ext.MessageEvent.XMLNS );
            if( msgEvent != null ) {
                //cr.appendMessage( "dude is typing" );
                var event = msgEvent.getEvent();
//;;;                cr.appendNotice( "Event: " +  (event != Xmpp4Js.Ext.MessageEvent.EVENT_EMPTY ? event: "empty") );
            } 
            
            var chatState = message.getExtension( Xmpp4Js.Ext.ChatStates.XMLNS );
            if( chatState != null ) {
                var state = chatState.getState();
//;;;                cr.appendNotice( "Chat State: " + state );
            }
            
            var tokBoxReq = message.getExtension( Soashable.Ext.TokBox.XMLNS );
            if( tokBoxReq != null ) {
                var widget = tokBoxReq.getWidget();
                
                Ext.Ajax.request({
                    url: "/app/tokbox", 
                    method: "GET",
                    params:{
                        op: "getInfo", 
                        email: "testing2@soashable.com"
                    },
                    scope: this,
                    callback: this.onTokBoxServletResponse_Join,
                    options: {
                        remoteWidget: widget // we open the box to their widget
                    }
                });    
            }
        },
        
        /**
         * 
         * @param chat {Xmpp4Js.Chat.Chat} The chat the message is related to
         * @param message {Xmpp4Js.Packet.Message} The incoming message
         * @private
         */
        onMessageSent : function( chat, message ) {
            var cr = Ext.ComponentMgr.get( this.chatRendererId );
            cr.appendMessage( chat.getOutgoingJid(), message.getBody(), "blue" );
            cr.scrollToBottom();
        },
        onChatClosed : function( chat ) {
            var cr = Ext.ComponentMgr.get( this.chatRendererId );
            cr.appendNotice( "The chat was closed." ); 
            
            this.ownerCt.remove( this, true );
        },
    
        onAddContact: function() {
            var addContactDlg = this.dialogManager.createAddContactDialog({
                //initialGroup: // TODO some kind of default group... selection, etc.
                initialUsername: this.chat.getParticipant().withoutResource().toString()
            });
            addContactDlg.show();
        }
}

Ext.extend( Soashable.Dialog.ChatDialog.Tab, Ext.Panel, Soashable.Dialog.ChatDialog.Tab.prototype);


// Copyright (C) 2007  Harlan Iverson <h.iverson at gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


Ext.namespace( "Soashable.Dialog.RegistrationDialog" );

/**
 * Initialize RosterWindow and add onRosterUpdate as a listener to roster, and
 * reload roster to get all updates.
 *
 * @param roster Roster the roster to bind to
 */
Soashable.Dialog.RegistrationDialog.Default = function( config ) {

    this.formId = Ext.id();
    this.usernameId = Ext.id();
    this.passwordId = Ext.id();
    
    this.con = config.con;
    this.lang = config.lang;


    var superConfig = Ext.apply( config, {
        width:300,
        height:150,
        title: this.lang.registerTitle,
        renderTo: document.body,

        layout: "fit",
        items: [{
            id: this.formId,
            xtype: "form",
            
            onSubmit: Ext.emptyFn,
            submit: Ext.emptyFn,
            
            defaultType: 'textfield',
            defaults: {
                    // applied to each contained item
                    width: 130,
                    msgTarget: 'side'
            },
            items: [
            {
                    fieldLabel: this.lang.username,
                    name: 'username',
                    allowBlank: false,

                    id: this.usernameId
            },{
                    fieldLabel: this.lang.password,
                    name: 'password',
                    allowBlank: false,
                    inputType: "password",

                    id: this.passwordId
            }/*,{
                    fieldLabel: 'Email',
                    name: 'email',
                    allowBlank: true
            },{
                    fieldLabel: 'Name',
                    name: 'name',
                    allowBlank: true
            }*/
                /*new DataFormView({
                    dataForm: this.dataForm, 
                    formId: "register", 
                    template: this.template/*,
                    listeners: {
                        scope: this,
                        submit : this._onSubmit,
                        cancel : this._onCancel   
                    }
                })*/

            ]
        }],
        buttons: [
        {
            text: this.lang.cancelButton,
            scope: this,
            handler: this.onCancel
        },
        {
            text: this.lang.registerButton,
            scope: this,
            handler: this.onRegister
        }
        ]
    });

    Soashable.Dialog.RegistrationDialog.Default.superclass.constructor.call(this, superConfig);
    
    this.addEvents({
        regerror: true,
        regsuccess: true
    });
}

Ext.extend(Soashable.Dialog.RegistrationDialog.Default, Ext.Window, {
    /**
     * This logic should maybe go somewhere else, but it works. It should
     * also use the DataForm if available.
     * @private
     */
    onRegister: function() {
        var formVals = Ext.ComponentMgr.get( this.formId ).getForm().getValues();

        var regFlow = new Xmpp4Js.Workflow.Registration({
        	con: this.con,
        	//toJid: this.con.jid /* TODO make getJid() */,
        	listeners: {
        		scope: this,
        		success: this.onSuccess,
        		failure: this.onFailure
        	}
       	});
       	
       	regFlow.start( formVals );
    },
    
    /**
     * @private
     */
    onSuccess: function(responseIq) {
    	this.fireEvent( "regsuccess", responseIq );
    },
    
    /**
     * @private
     */
    onFailure: function(responseIq) {
    	this.fireEvent( "regerror", responseIq );
    },
    
    /**
     * @private
     */
    onCancel: function() {
        this.hide();
    }
});


Ext.namespace( "Soashable.Dialog" );


/**
 * @class
 */
Soashable.Dialog.BlogDialog = function(config) {
    this.lang = config.lang;
    
    var superConfig = Ext.apply( config, {
        title: "Soashable Blog",
        autoLoad: "/blog",
        width: 300,
        height: 300,
        bodyStyle: 'padding: 7px',
        autoScroll: true
        
    });

    Soashable.Dialog.BlogDialog.superclass.constructor.call( this, superConfig );
}

Soashable.Dialog.BlogDialog.prototype = {

}

Ext.extend( Soashable.Dialog.BlogDialog, Ext.Window, Soashable.Dialog.BlogDialog.prototype );
Ext.namespace( "Soashable.Dialog" );


/**
 * @class
 */
Soashable.Dialog.AddBuddyDialog = function(config) {
    this.title = config.title;
    this.lang = config.lang;
    this.groups = config.groups;
    
    this.initialGroup = config.initialGroup ? config.initialGroup : undefined;
    this.initialUsername = config.initialUsername ? config.initialUsername : undefined;
    this.initialAlias = config.initialAlias ? config.initialAlias : undefined;
    
    
    this.usernameId = "addbuddy-username";
    this.groupId = "addbuddy-group";
    this.aliasId = "addbuddy-alias";
    
    var superConfig = Ext.apply( config, {
        title: this.title,
        width: 310,
        height: 167,

        layout: 'form',
        title: this.title, // cancels out the title we passed in, because that makes it ugly using ext panel title.
        defaultType: 'textfield',
        defaults: {
            // applied to each contained item
            width: 150,
            msgTarget: 'side'
        },

        items: [
        {
            fieldLabel: this.lang.username,
            name: 'username',
            allowBlank: false,
            emptyText: this.lang.enterUsernameBlank,
            value: this.initialUsername,

            id: this.usernameId
        },{
            fieldLabel: this.lang.alias,
            name: 'alias',
            allowBlank: true,
            value: this.initialAlias,

            id: this.aliasId
        },{
            xtype: 'combo',
            id: this.groupId,
            fieldLabel: this.lang.group,
            mode: 'local',
            store: this.getGroupStore(),
            value: this.initialGroup,
            tpl: Soashable.Dialog.AddBuddyDialog.showTemplate,
            displayField: 'group',
            valueField: 'group',
            editable: false,
            forceSelection: true,
            triggerAction: 'all',
            width: 150
        }
        
        ],

        buttons: [{
            id: "addbuddy-ok-button",
            text: this.lang.addContactButton,
            scope: this,
            handler: this.onAddBuddyClicked
        }]
    });
    
    this.addEvents({
        /**
         * @event addbuddy
         * @param {String} jid
         * @param {String} groupName
         * @param {String} alias
         */
        addbuddy: true
    })

    Soashable.Dialog.AddBuddyDialog.superclass.constructor.call( this, superConfig );
}

/**
 * The template to be shown in the status dropdown. Has 'text', 'show', 'icon' and 'color tokens.
 */
Soashable.Dialog.AddBuddyDialog.showTemplate = '<tpl for="."><div class="x-combo-list-item">{group}</div></tpl>';


Soashable.Dialog.AddBuddyDialog.prototype = {
    /**
     * @private
     */
     onAddBuddyClicked: function() {
        var usernameField = Ext.ComponentMgr.get(this.usernameId);
        var groupField = Ext.ComponentMgr.get(this.groupId);
        var aliasField = Ext.ComponentMgr.get(this.aliasId);

        var jid = usernameField.getValue();
        var groupName = groupField.getValue();
        var alias = aliasField.getValue();
        
        this.fireEvent( "addbuddy", jid, groupName, alias );
    },

    /**
     * Creaets and returns a store with all status states, merged
     * with the lang object:
     * row data: 'text', 'show', 'icon', and 'color' fields.
     * @private
     */
    getGroupStore: function() {
        
        var data = [];
        for( var i = 0; i < this.groups.length; i++ ) {
            var groupName = this.groups[i];
            
            data.push( [groupName] );
        }
        
        var config = {
            fields: ['group'],
            // TODO generate data from the actual roster
            data : data
        }

        return new Ext.data.SimpleStore(config);
    }
}

Ext.extend( Soashable.Dialog.AddBuddyDialog, Ext.Window, Soashable.Dialog.AddBuddyDialog.prototype );

var JABBER_DOMAIN = "soashable.com";

// single global.
var soashable;

Ext.onReady( function() {
    
    if( Ext.isGecko || Ext.isIE || Ext.isOpera || Ext.isSafari) {
        // assign to global.
        soashable = new Soashable(Soashable.Lang.EN);
        soashable.start();
    } else {
        Ext.MessageBox.show({
            title: "Compatibility", 
            msg: "Sorry, this application is only known to work on "+
                "Firefox 2.x, IE7 and Opera. If you wanna be a good sport and help out, "+
                "<a href='http://code.google.com/p/soashable/issues/detail?id=16' "+
                "target='_blank'>here is the ticket</a>.",
            buttons: false,
            icon: Ext.MessageBox.ERROR,
            closable: false,
            modal: false
        });
    }
    
    var blogDlg = new Soashable.Dialog.BlogDialog({lang: this.lang});
    blogDlg.setPosition( 232, 112 );
    blogDlg.show();
    blogDlg.toBack();

});
